#pragma once

#include <iostream>
#include <sys/select.h>
#include <unordered_map>
#include <functional>

#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace socket_ns;

/*
FD_ZERO：将指定的fd_set清零
FD_SET：将需要测试的fd加入fd_set
FD_ISSET：用于测试指定fd是否在该fd_set中。
FD_CLR：用于在fd集合(fd_set)中删除一个fd。

用法：
    fd_set set;
    FD_ZERO(&set);
    FD_SET(fd, &set);
    FD_CLR(fd, &set);
    FD_ISSET(fd, &set);

--------------------------------------------------------------------------------------------------------

    int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout);

    返回对应位仍然为1的fd的总数。<0时错误为EINTR，认为连接是正常的。

    nfds
    需要检查的最大文件描述字+1
    readset
    用来检查可读性的一组文件描述字。
    writeset
    用来检查可写性的一组文件描述字。
    exceptset
    用来检查是否有异常条件出现的文件描述字。(注：错误不包括在异常条件之内)
    timeout
    用于描述一段时间长度，如果在这个时间内，需要监视的描述符没有事件发生则函数返回，返回值为0。
    有三种可能：
      1.timeout=NULL（阻塞：select将一直被阻塞，直到某个文件描述符上发生了事件）
      2.timeout所指向的结构设为非零时间（等待固定时间：如果在指定的时间段里有事件发生或者时间耗尽，函数均返回）
      3.timeout所指向的结构，时间设为0（非阻塞：仅检测描述符集合的状态，然后立即返回，并不等待外部事件的发生）
*/

using task_t = std::function<std::string(std::string &)>;

class SelectServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    SelectServer(uint16_t port)
        : _port(port), _listensock(std::make_unique<TcpSocket>())
    {
        _listensock->BuildListenSocket(_port);
    }

    void InitServer()
    {
        for (int i = 0; i < gnum; ++i)
        {
            fd_array[i] = gdefaultfd;
        }
        fd_array[0] = _listensock->Sockfd();
    }

    // 处理新连接
    void Accepter()
    {
        InetAddr addr;
        SockSPtr sockfd = _listensock->Accepter(&addr);
        if (sockfd->Sockfd() > 0)
        {
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.Ip().c_str(), addr.port());
            bool flag = false;
            for (int pos = 0; pos < gnum; ++pos)
            {
                if (fd_array[pos] == gdefaultfd)
                {
                    flag = true;
                    fd_array[pos] = sockfd->Sockfd();
                    LOG(INFO, "add %d to fd_array success!\n", sockfd->Sockfd());
                    break;
                }
            }
            if (!flag)
            {
                LOG(WARNING, "Server Is Full!\n");
                ::close(sockfd->Sockfd());
            }
        }
    }

    // 处理普通的就绪fd
    void HandlerIO(int i)
    {
        char inbuffer[1024];
        size_t n = ::recv(fd_array[i], inbuffer, sizeof(inbuffer) - 1, 0);
        if (n > 0)
        {
            inbuffer[n] = 0;
            std::cout << "client say# " << inbuffer << std::endl;
            // std::string content = "<html><body><h1>test selectserver</h1></body></html>";
            // std::string echo_str = "HTTP/1.0 200 OK\r\n";
            // echo_str += "Content-Type: text/html\r\n";
            // echo_str += "Content-Length: " + std::to_string(content.size()) + "\r\n\r\n";
            // echo_str += content;

            std::string temp(inbuffer);
            std::string echo_str = _TackProcess(temp);

            ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0);
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            ::close(fd_array[i]);
            // 取消select关心
            fd_array[i] = gdefaultfd;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            ::close(fd_array[i]);
            fd_array[i] = gdefaultfd;
        }
    }

    void HandlerEvent(fd_set &rfds)
    {
        // 事件派发
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            // 检查在select函数返回后，某个描述符是否准备好
            if (FD_ISSET(fd_array[i], &rfds))
            {
                if (_listensock->Sockfd() == fd_array[i])
                {
                    // 监听套接字接入新连接
                    Accepter();
                }
                else
                {
                    // 普通套接字事件就绪
                    HandlerIO(i);
                }
            }
        }
    }

    void Loop()
    {
        while (true)
        {
            // 位图结构
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = gdefaultfd;

            for (int i = 0; i < gnum; i++)
            {
                if (fd_array[i] == gdefaultfd)
                    continue;
                FD_SET(fd_array[i], &rfds);
                if (max_fd < fd_array[i])
                {
                    max_fd = fd_array[i];
                }
            }

            struct timeval timeout = {30, 0};
            int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, &timeout);
            switch (n)
            {
            case 0:
                LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                break;
            case -1:
                LOG(ERROR, "select error\n");
                break;
            default:
                // 如果事件就绪，但是不处理，select就会一直通知
                LOG(INFO, "haved event ready, n : %d\n", n);
                // 进行任务派发
                HandlerEvent(rfds);
                PrintDebug();
                sleep(1);
                break;
            }
        }
    }

    void SetTackProcess(const task_t &handle)
    {
        _TackProcess = handle;
    }

    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_array[i] == gdefaultfd)
                continue;
            std::cout << fd_array[i] << " ";
        }
        std::cout << "\n";
    }

    ~SelectServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int fd_array[gnum];

    task_t _TackProcess;
};
